# 导入json模块，用于处理JSON数据
import json
import uuid

# 从qtpy库导入QtCore模块，提供核心的非GUI功能
from qtpy import QtCore
# 从qtpy库导入QtGui模块，提供图形用户界面相关的功能
from qtpy import QtGui
# 从qtpy.QtWidgets模块导入各种界面组件
from qtpy.QtWidgets import QWidget
from qtpy.QtWidgets import QLineEdit
from qtpy.QtWidgets import QComboBox
from qtpy.QtWidgets import QHBoxLayout
from qtpy.QtWidgets import QLabel
from qtpy.QtWidgets import QSpacerItem
from qtpy.QtWidgets import QSizePolicy
from qtpy.QtWidgets import QPushButton
from qtpy.QtWidgets import QInputDialog

# 从src模块导入获取可哈希数据类型的函数
from src import getHashableDataTypes
# 从src.Core.Common模块导入所有内容
from src.Core.Common import *
# 从src.UI.EditorHistory模块导入编辑器历史记录类
from src.UI.EditorHistory import EditorHistory
# 从src.UI.UIInterfaces模块导入属性视图支持接口
from src.UI.UIInterfaces import IPropertiesViewSupport
# 从src.UI.Widgets.InputWidgets模块导入创建输入控件的函数
from src.UI.Widgets.InputWidgets import createInputWidget
# 从src.UI.Widgets.PropertiesFramework模块导入可折叠表单控件类
from src.UI.Widgets.PropertiesFramework import CollapsibleFormWidget
# 从src模块导入根据类型获取引脚默认值的函数
from src import getPinDefaultValueByType
# 从src.UI.Widgets.EnumComboBox模块导入枚举组合框类
from src.UI.Widgets.EnumComboBox import EnumComboBox
# 从src模块导入根据类型查找引脚类的函数
from src import findPinClassByType
# 从src模块导入获取所有引脚类的函数
from src import getAllPinClasses

# 带颜色的圆角矩形控件
# 颜色对应引脚数据类型的颜色
class TypeWidget(QWidget):
    """
    继承自QWidget，用于绘制与引脚数据类型颜色对应的彩色圆角矩形。

    :param keyColor: 键的颜色
    :param valueColor: 值的颜色
    :param parent: 父控件
    """
    def __init__(self, keyColor, valueColor, parent=None):
        super(TypeWidget, self).__init__(parent=parent)
        # 存储键的颜色
        self.keyColor = keyColor
        # 存储值的颜色
        self.valueColor = valueColor
        # 设置控件的最小宽度
        self.setMinimumWidth(24)

    def paintEvent(self, event):
        """
        重写绘制事件，根据不同的结构类型绘制不同的图形。

        :param event: 绘制事件对象
        """
        # 创建一个QPainter对象用于绘制
        painter = QtGui.QPainter()
        # 开始在当前控件上绘制
        painter.begin(self)

        # 根据父控件的数据类型查找引脚类并获取颜色，然后创建QColor对象
        keyColor = QtGui.QColor.fromRgb(
            *findPinClassByType(self.parent().dataType).color()
        )

        # 获取父控件的原始变量结构
        structure = self.parent()._rawVariable.structure
        if structure == StructureType.Single:
            # 设置绘制抗锯齿以提高图形质量
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            # 创建一个画笔对象
            pen = QtGui.QPen()
            # 设置画笔颜色为透明
            pen.setColor(QtGui.QColor(0, 0, 0, 0))
            # 设置绘制使用的画笔
            painter.setPen(pen)
            # 获取绘制事件的矩形区域
            rect = event.rect()
            # 设置矩形的高度
            rect.setHeight(10)
            # 设置矩形的宽度
            rect.setWidth(15)
            # 移动矩形的顶部位置
            rect.moveTop(3)
            # 设置绘制使用的画刷颜色
            painter.setBrush(keyColor)
            # 绘制圆角矩形
            painter.drawRoundedRect(rect, 5, 5)

        if structure == StructureType.Array:
            # 定义网格大小
            gridSize = 3
            # 获取控件的高度
            size = self.height()
            # 计算每个单元格的宽度
            cellW = size / gridSize
            # 计算每个单元格的高度
            cellH = size / gridSize

            # 设置绘制使用的画刷
            painter.setBrush(QtGui.QBrush(keyColor))
            # 设置绘制使用的画笔
            painter.setPen(QtGui.QPen(QtCore.Qt.black, 0.2))
            # 遍历网格的行
            for row in range(gridSize):
                # 遍历网格的列
                for column in range(gridSize):
                    # 计算当前单元格的x坐标
                    x = row * cellW
                    # 计算当前单元格的y坐标
                    y = column * cellH
                    # 绘制矩形
                    painter.drawRect(x, y, cellW, cellH)

        if structure == StructureType.Dict:
            # 获取父控件原始变量的值
            dictObject = self.parent()._rawVariable.value

            # 根据字典对象的键类型查找引脚类并获取颜色，然后创建QColor对象
            keyColor = QtGui.QColor.fromRgb(
                *findPinClassByType(dictObject.keyType).color()
            )
            # 根据字典对象的值类型查找引脚类并获取颜色，然后创建QColor对象
            valueColor = QtGui.QColor.fromRgb(
                *findPinClassByType(dictObject.valueType).color()
            )

            # 定义网格大小
            gridSize = 3
            # 获取控件的高度
            size = self.height()
            # 计算每个单元格的宽度
            cellW = size / gridSize
            # 计算每个单元格的高度
            cellH = size / gridSize

            # 设置绘制使用的画刷
            painter.setBrush(QtGui.QBrush(keyColor))
            # 设置绘制使用的画笔
            painter.setPen(QtGui.QPen(QtCore.Qt.black, 0.2))
            # 遍历网格的行
            for row in range(gridSize):
                # 设置绘制使用的画刷为键的颜色
                painter.setBrush(keyColor)
                # 绘制键对应的矩形
                painter.drawRect(0, row * cellH, cellW, cellH)
                # 设置绘制使用的画刷为值的颜色
                painter.setBrush(valueColor)
                # 绘制值对应的矩形
                painter.drawRect(cellW, row * cellH, cellW * 2, cellH)

        # 结束绘制
        painter.end()

# 变量类，继承自QWidget和IPropertiesViewSupport接口
class UIVariable(QWidget, IPropertiesViewSupport):
    """
    表示UI中的变量，继承自QWidget和IPropertiesViewSupport接口。

    :param rawVariable: 原始变量对象
    :param variablesWidget: 变量控件对象
    :param parent: 父控件
    """
    def __init__(self, rawVariable, variablesWidget, parent=None):
        super(UIVariable, self).__init__(parent)
        # 存储原始变量对象
        self._rawVariable = rawVariable
        # 存储变量控件对象
        self.variablesWidget = variablesWidget
        # ui设置
        # 创建一个水平布局
        self.horizontalLayout = QHBoxLayout(self)
        # 设置布局的间距
        self.horizontalLayout.setSpacing(1)
        # 设置布局的边距
        self.horizontalLayout.setContentsMargins(1, 1, 1, 1)
        # 设置布局的对象名称
        self.horizontalLayout.setObjectName("horizontalLayout")
        # 创建一个TypeWidget控件
        self.widget = TypeWidget(
            findPinClassByType(self._rawVariable.dataType).color(), self
        )
        # 设置控件的对象名称
        self.widget.setObjectName("widget")
        # 将TypeWidget控件添加到布局中
        self.horizontalLayout.addWidget(self.widget)
        # 创建一个标签控件
        self.labelName = QLabel(self)
        # 设置标签的样式表为透明背景
        self.labelName.setStyleSheet("background:transparent")
        # 设置标签的对象名称
        self.labelName.setObjectName("labelName")
        # 将标签控件添加到布局中
        self.horizontalLayout.addWidget(self.labelName)
        # 创建一个间隔项
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        # 将间隔项添加到布局中
        self.horizontalLayout.addItem(spacerItem)
        # 查找引用按钮
        # 创建一个查找引用的按钮
        self.pbFindRefs = QPushButton("")
        # 设置按钮的图标
        self.pbFindRefs.setIcon(QtGui.QIcon(":/searching-magnifying-glass.png"))
        # 设置按钮的对象名称
        self.pbFindRefs.setObjectName("pbFindRefs")
        # 将按钮添加到布局中
        self.horizontalLayout.addWidget(self.pbFindRefs)
        # 连接按钮的点击信号到查找引用的槽函数
        self.pbFindRefs.clicked.connect(self.onFindRefsClicked)
        # 删除变量按钮
        # 创建一个删除变量的按钮
        self.pbKill = QPushButton("")
        # 设置按钮的图标
        self.pbKill.setIcon(QtGui.QIcon(":/delete_icon.png"))
        # 设置按钮的对象名称
        self.pbKill.setObjectName("pbKill")
        # 将按钮添加到布局中
        self.horizontalLayout.addWidget(self.pbKill)
        # 连接按钮的点击信号到删除变量的槽函数
        self.pbKill.clicked.connect(self.onKillClicked)

        # 自动连接信号和槽
        QtCore.QMetaObject.connectSlotsByName(self)
        # 设置变量的名称
        self.setName(self._rawVariable.name)
        # 设置原始变量的包装器为当前对象
        self._rawVariable.setWrapper(self)

    def onStructureChanged(self, name):
        """
        当变量结构发生变化时调用。

        :param name: 新的结构类型名称
        """
        # 设置原始变量的结构类型
        self._rawVariable.structure = StructureType[name]
        # 请求填充属性视图
        self.variablesWidget.pyFlowInstance.onRequestFillProperties(
            self.createPropertiesWidget
        )
        # 保存编辑器历史状态
        EditorHistory().saveState("Change variable struct", modify=True)
        # 更新TypeWidget控件
        self.widget.update()

    def setDataType(self, dataType):
        """
        设置变量的数据类型。

        :param dataType: 新的数据类型
        """
        # 设置当前对象的数据类型
        self.dataType = dataType
        # 设置原始变量的数据类型
        self._rawVariable.dataType = dataType
        # 保存编辑器历史状态
        EditorHistory().saveState("Change variable data type", modify=True)

    def onDictKeyTypeChanged(self, newType):
        """
        当字典变量的键类型发生变化时调用。

        :param newType: 新的键类型
        """
        # 获取原始变量的值
        dictObject = self._rawVariable.value
        # 设置字典对象的键类型
        dictObject.keyType = newType
        # 更新TypeWidget控件
        self.widget.update()

    def onDictValueTypeChanged(self, newType):
        """
        当字典变量的值类型发生变化时调用。

        :param newType: 新的值类型
        """
        # 获取原始变量的值
        dictObject = self._rawVariable.value
        # 设置字典对象的值类型
        dictObject.valueType = newType
        # 更新TypeWidget控件
        self.widget.update()

    def createPropertiesWidget(self, propertiesWidget):
        """
        创建属性视图控件。

        :param propertiesWidget: 属性视图控件
        """
        # 创建一个基础分类的可折叠表单控件
        baseCategory = CollapsibleFormWidget(headName="Base")
        # 名称
        # 创建一个用于输入名称的行编辑框
        le_name = QLineEdit(self._rawVariable.name)
        # 连接行编辑框的回车信号到设置名称的方法
        le_name.returnPressed.connect(lambda: self.setName(le_name.text()))
        # 将行编辑框添加到基础分类中
        baseCategory.addWidget("Name", le_name)

        # 数据类型
        if self._rawVariable.structure == StructureType.Dict:
            # 如果是字典结构，获取原始变量的值
            dictObject = self._rawVariable.value
            # 创建一个用于选择键类型的枚举组合框
            keyTypeSelector = EnumComboBox(values=getHashableDataTypes())
            # 创建一个用于选择值类型的枚举组合框
            valueTypeSelector = EnumComboBox(
                [
                    pin.__name__
                    for pin in getAllPinClasses()
                    if pin.IsValuePin()
                    if pin.__name__ != "AnyPin"
                ]
            )

            # 设置键类型选择器不可编辑
            keyTypeSelector.setEditable(False)
            # 设置值类型选择器不可编辑
            valueTypeSelector.setEditable(False)

            # 设置键类型选择器的当前索引
            keyTypeSelector.setCurrentIndex(
                keyTypeSelector.findText(dictObject.keyType)
            )
            # 设置值类型选择器的当前索引
            valueTypeSelector.setCurrentIndex(
                valueTypeSelector.findText(dictObject.valueType)
            )

            # 连接键类型选择器的变化信号到键类型变化的槽函数
            keyTypeSelector.changeCallback.connect(self.onDictKeyTypeChanged)
            # 连接值类型选择器的变化信号到值类型变化的槽函数
            valueTypeSelector.changeCallback.connect(self.onDictValueTypeChanged)

            # 将键类型选择器添加到基础分类中
            baseCategory.addWidget("Key type", keyTypeSelector)
            # 将值类型选择器添加到基础分类中
            baseCategory.addWidget("Value type", valueTypeSelector)
        else:
            # 如果不是字典结构，创建一个用于选择数据类型的枚举组合框
            cbTypes = EnumComboBox(
                [
                    pin.__name__
                    for pin in getAllPinClasses()
                    if pin.IsValuePin()
                    if pin.__name__ != "AnyPin"
                ]
            )
            # 设置数据类型选择器的当前索引
            cbTypes.setCurrentIndex(cbTypes.findText(self.dataType))
            # 连接数据类型选择器的变化信号到设置数据类型的槽函数
            cbTypes.changeCallback.connect(self.setDataType)
            # 设置数据类型选择器不可编辑
            cbTypes.setEditable(False)
            # 将数据类型选择器添加到基础分类中
            baseCategory.addWidget("Type", cbTypes)
        # 将基础分类添加到属性视图控件中
        propertiesWidget.addWidget(baseCategory)

        # 结构类型
        # 创建一个用于选择结构类型的枚举组合框
        cbStructure = EnumComboBox(
            [
                i.name
                for i in (StructureType.Single, StructureType.Array, StructureType.Dict)
            ]
        )
        # 设置结构类型选择器不可编辑
        cbStructure.setEditable(False)
        # 设置结构类型选择器的当前索引
        cbStructure.setCurrentIndex(
            cbStructure.findText(self._rawVariable.structure.name)
        )
        # 连接结构类型选择器的变化信号到结构变化的槽函数
        cbStructure.changeCallback.connect(self.onStructureChanged)
        # 将基础分类添加到属性视图控件中
        propertiesWidget.addWidget(baseCategory)
        # 将结构类型选择器添加到基础分类中
        baseCategory.addWidget("Structure", cbStructure)

        # 创建一个值分类的可折叠表单控件
        valueCategory = CollapsibleFormWidget(headName="Value")

        # 当前值
        if self._rawVariable.structure == StructureType.Single:
            if not type(self._rawVariable.value) in {list, set, dict, tuple}:

                def valSetter(x):
                    """
                    设置原始变量的值。

                    :param x: 新的值
                    """
                    self._rawVariable.value = x

                # 创建一个输入控件
                w = createInputWidget(
                    self._rawVariable.dataType,
                    valSetter,
                    getPinDefaultValueByType(self._rawVariable.dataType),
                )
                if w:
                    # 设置输入控件的值
                    w.setWidgetValue(self._rawVariable.value)
                    # 设置输入控件的对象名称
                    w.setObjectName(self._rawVariable.name)
                    # 将输入控件添加到值分类中
                    valueCategory.addWidget(self._rawVariable.name, w)

        # 访问级别
        # 创建一个组合框用于选择访问级别
        cb = QComboBox()
        # 添加公共访问级别选项
        cb.addItem("public", 0)
        # 添加私有访问级别选项
        cb.addItem("private", 1)
        # 添加受保护访问级别选项
        cb.addItem("protected", 2)

        def accessLevelChanged(x):
            """
            当访问级别发生变化时调用。

            :param x: 新的访问级别名称
            """
            # 设置原始变量的访问级别
            self._rawVariable.accessLevel = AccessLevel[x]
            # 保存编辑器历史状态
            EditorHistory().saveState("Change variable access level", modify=True)

        # 连接组合框的文本变化信号到访问级别变化的槽函数
        cb.currentTextChanged.connect(accessLevelChanged)
        # 设置组合框的当前索引
        cb.setCurrentIndex(self._rawVariable.accessLevel)
        # 将组合框添加到值分类中
        valueCategory.addWidget("Access level", cb)
        # 将值分类添加到属性视图控件中
        propertiesWidget.addWidget(valueCategory)

    def onFindRefsClicked(self):
        """
        当查找引用按钮被点击时调用。
        """
        # 从PyFlow.App模块导入PyFlow类
        from src.App import PySC

        # 获取原始变量的引用列表
        refs = [n.getWrapper() for n in self._rawVariable.findRefs()]
        # 获取应用实例
        app = self.variablesWidget.pyFlowInstance
        if "Search results" not in [t.name() for t in app.getRegisteredTools()]:
            # 如果搜索结果工具未注册，则调用该工具
            app.invokeDockToolByName("PyFlowBase", "Search results")
        # 发出显示搜索结果的请求信号
        self.variablesWidget.pyFlowInstance.getCanvas().requestShowSearchResults.emit(
            refs
        )

    def onKillClicked(self):
        """
        当删除变量按钮被点击时调用。
        """
        # 检查引用并询问用户如何处理
        # 获取原始变量的引用列表
        refs = self._rawVariable.findRefs()
        if len(refs) > 0:
            # 如果有引用，弹出输入对话框让用户选择处理方式
            item, accepted = QInputDialog.getItem(
                None,
                "Decide!",
                "What to do with getters and setters in canvas?",
                ["kill", "leave"],
                editable=False,
            )
            if accepted:
                # 如果用户确认，删除当前变量
                self.variablesWidget.killVar(self)
                if item == "kill":
                    # 如果用户选择删除引用，则删除所有引用
                    for i in refs:
                        i.kill()
                elif item == "leave":
                    # 如果用户选择保留引用，则将引用的变量设置为None
                    for i in refs:
                        i.var = None
        else:
            # 如果没有引用，直接删除当前变量
            self.variablesWidget.killVar(self)

    @property
    def dataType(self):
        """
        获取原始变量的数据类型。

        :return: 数据类型
        """
        return self._rawVariable.dataType

    @dataType.setter
    def dataType(self, value):
        """
        设置原始变量的数据类型。

        :param value: 新的数据类型
        """
        self._rawVariable.dataType = value
        # 更新TypeWidget控件的颜色
        self.widget.color = findPinClassByType(self._rawVariable.dataType).color()
        # 更新TypeWidget控件
        self.widget.update()
        # 通知变量控件更新属性视图
        self.variablesWidget.onUpdatePropertyView(self)

    @property
    def packageName(self):
        """
        获取原始变量的包名。

        :return: 包名
        """
        return self._rawVariable.packageName

    @property
    def accessLevel(self):
        """
        获取原始变量的访问级别。

        :return: 访问级别
        """
        return self._rawVariable.accessLevel

    @accessLevel.setter
    def accessLevel(self, value):
        """
        设置原始变量的访问级别。

        :param value: 新的访问级别
        """
        self._rawVariable.accessLevel = value

    @property
    def uid(self):
        """
        获取原始变量的唯一标识符。

        :return: 唯一标识符
        """
        return self._rawVariable.uid

    @uid.setter
    def uid(self, value):
        """
        设置原始变量的唯一标识符。

        :param value: 新的唯一标识符
        """
        self._rawVariable.uid = value
        if self._rawVariable.uid in self.graph.getVars():
            # 如果唯一标识符已存在于图的变量中，则移除并重新添加
            self.graph.getVars().pop(self._rawVariable.uid)
            self.graph.getVars()[self._rawVariable.uid] = self._rawVariable

    @staticmethod
    def jsonTemplate():
        """
        获取变量的JSON模板。

        :return: JSON模板字典
        """
        template = {
            "name": None,
            "uuid": None,
            "value": None,
            "type": None,
            "package": None,
            "accessLevel": None,
        }
        return template

    def serialize(self):
        """
        将变量序列化为JSON格式。

        :return: 序列化后的JSON字典
        """
        # 根据原始变量的数据类型查找引脚类
        pinClass = findPinClassByType(self._rawVariable.dataType)

        # 获取JSON模板
        template = UIVariable.jsonTemplate()
        # 设置模板中的名称
        template["name"] = self._rawVariable.name
        # 设置模板中的唯一标识符
        template["uuid"] = str(self._rawVariable.uid)

        if self._rawVariable.dataType == "AnyPin":
            # 如果数据类型为AnyPin，则不保存值
            template["value"] = None
        else:
            # 否则，将原始变量的值序列化为JSON字符串
            template["value"] = json.dumps(
                self._rawVariable.value, cls=pinClass.jsonEncoderClass()
            )

        # 设置模板中的数据类型
        template["type"] = self._rawVariable.dataType
        # 设置模板中的包名
        template["package"] = self._rawVariable.packageName
        # 设置模板中的访问级别
        template["accessLevel"] = self._rawVariable.accessLevel.value
        return template

    @staticmethod
    def deserialize(data, graph):
        """
        从JSON数据反序列化为变量对象。

        :param data: JSON数据
        :param graph: 图对象
        :return: 反序列化后的变量对象
        """
        # 根据数据类型查找引脚类
        pinClass = findPinClassByType(data["dataType"])

        # 创建唯一标识符对象
        varUid = uuid.UUID(data["uuid"])
        # 创建变量对象
        var = graph.getApp().variablesWidget.createVariable(
            dataType=data["dataType"],
            accessLevel=AccessLevel(data["accessLevel"]),
            uid=varUid,
        )
        # 设置变量的名称
        var.setName(data["name"])
        # 设置变量的数据类型
        var.setDataType(data["dataType"])

        if data["dataType"] == "AnyPin":
            # 如果数据类型为AnyPin，则设置默认值
            var.value = getPinDefaultValueByType("AnyPin")
        else:
            # 否则，从JSON数据中加载值
            var.value = json.loads(data["value"], cls=pinClass.jsonDecoderClass())

        return var

    @property
    def value(self):
        """
        获取原始变量的值。

        :return: 变量的值
        """
        return self._rawVariable.value

    @value.setter
    def value(self, data):
        """
        设置原始变量的值。

        :param data: 新的值
        """
        self._rawVariable.value = data

    def mousePressEvent(self, event):
        """
        处理鼠标按下事件。

        :param event: 鼠标事件对象
        """
        super(UIVariable, self).mousePressEvent(event)
        # 通知变量控件更新属性视图
        self.variablesWidget.onUpdatePropertyView(self)

    def setName(self, name):
        """
        设置变量的名称。

        :param name: 新的名称
        """
        self._rawVariable.name = name
        # 设置标签的文本为新的名称
        self.labelName.setText(self._rawVariable.name)